#include <QFile>
#include <QDebug>

#include "op_dispatcher.h"

union
{
    uint8_t bin[4];
    int32_t result;
    float p_result;
} buf_int32;

int read_int32(QFile &file)
{
    file.read(reinterpret_cast<char *>(buf_int32.bin), 4);
    return buf_int32.result;
}

bool parse_bool(uint8_t *data, int &ptr)
{
    bool result = data[ptr] > 0;
    ++ptr;
    return result;
}

int parse_int32(uint8_t *data, int &ptr)
{
    memcpy(buf_int32.bin, data + ptr, 4);
    ptr += 4;
    return buf_int32.result;
}

float parse_float32(uint8_t *data, int &ptr)
{
    memcpy(buf_int32.bin, data, 4);
    ptr += 4;
    return buf_int32.p_result;
}

std::complex<float> parse_complex(uint8_t *data, int &ptr)
{
    return std::complex<float>(parse_float32(data, ptr),
                               parse_float32(data, ptr));
}

static uint8_t buf_aops[16];
static uint8_t buf_tops[37];
static uint8_t buf_eops[61];

void read_aops(QFile &file, AllocOp &op)
{
    file.read(reinterpret_cast<char *>(buf_aops), 16);
    int ptr = 0;
    op.data = parse_complex(buf_aops, ptr);
    op.addr = parse_int32(buf_aops, ptr);
    op.op_code = static_cast<AllocOpCode>(parse_int32(buf_aops, ptr));
}

void read_tops(QFile &file, TranOp &op)
{
    file.read(reinterpret_cast<char *>(buf_tops), 37);
    int ptr = 0;
    op.len_limit = parse_int32(buf_tops, ptr);
    op.op_code = static_cast<TranOpCode>(parse_int32(buf_tops, ptr));
    op.dst_peer_id = parse_int32(buf_tops, ptr);
    op.dst_addr = parse_int32(buf_tops, ptr);
    op.dst_stride = parse_int32(buf_tops, ptr);
    op.dst_clear = parse_bool(buf_tops, ptr);
    op.dst_sb_addr = parse_int32(buf_tops, ptr);
    op.dst_ain_addr = parse_int32(buf_tops, ptr);
    op.src_addr = parse_int32(buf_tops, ptr);
    op.src_stride = parse_int32(buf_tops, ptr);
}

void read_eops(QFile &file, ExecOp &op)
{
    file.read(reinterpret_cast<char *>(buf_eops), 61);
    int ptr = 0;
    op.alu = parse_int32(buf_eops, ptr);
    op.peer_id = parse_int32(buf_eops, ptr);
    op.len_limit = parse_int32(buf_eops, ptr);
    op.op_code = static_cast<AluOpCode>(parse_int32(buf_eops, ptr));
    op.dst_addr = parse_bool(buf_eops, ptr);
    op.dst_increase = parse_int32(buf_eops, ptr);
    op.dst_enable_offset = parse_bool(buf_eops, ptr);
    op.dst_peer_id = parse_int32(buf_eops, ptr);
    op.dst_stride = parse_int32(buf_eops, ptr);
    op.dst_sb = parse_bool(buf_eops, ptr);
    op.src_addr_0 = parse_int32(buf_eops, ptr);
    op.src_wait_passin_0 = parse_bool(buf_eops, ptr);
    op.src_wait_aluin_0 = parse_bool(buf_eops, ptr);
    op.src_enable_offset_0 = parse_bool(buf_eops, ptr);
    op.src_stride_0 = parse_int32(buf_eops, ptr);
    op.src_limit_0 = parse_int32(buf_eops, ptr);
    op.src_addr_1 = parse_int32(buf_eops, ptr);
    op.src_wait_passin_1 = parse_bool(buf_eops, ptr);
    op.src_wait_aluin_1 = parse_bool(buf_eops, ptr);
    op.src_enable_offset_1 = parse_bool(buf_eops, ptr);
    op.src_stride_1 = parse_int32(buf_eops, ptr);
    op.src_limit_1 = parse_int32(buf_eops, ptr);
}

OpDispatcher::OpDispatcher(Node *parent, const QString &path, bool visual) :
    Node(parent),
    top_sender(new ChannelTranOpFabricSender(this)),
    eop_sender(new ChannelExecOpFabricSender(this)),
    top_pointer(0),
    eop_pointer(0)
{
    // Read and parse the file.
    QFile junaFile(path);
    // Open the file.
    if(!junaFile.open(QIODevice::ReadOnly))
    {
        printf("Failed to open the binary execution.\n");
        exit(-1);
    }
    // Read the execution data.
    int aops_size = read_int32(junaFile);
    aops = QVector<AllocOp>(aops_size);
    for(int i=0; i<aops_size; ++i)
    {
        read_aops(junaFile, aops[i]);
    }
    int tops_size = read_int32(junaFile);
    tops = QVector<TranOp>(tops_size);
    for(int i=0; i<tops_size; ++i)
    {
        read_tops(junaFile, tops[i]);
    }
    int eops_size = read_int32(junaFile);
    eops = QVector<ExecOp>(eops_size);
    for(int i=0; i<eops_size; ++i)
    {
        read_eops(junaFile, eops[i]);
    }
    //Save visual state.
    this->visual = visual;
}

NodeTaskList OpDispatcher::tasks() const
{
    NodeTaskList list;
    list.append(static_cast<NodeTask>(&OpDispatcher::task_dispatch));
    return list;
}

void OpDispatcher::task_dispatch()
{
    if(!top_sender->Bus->is_any_full() && top_pointer < tops.size())
    {
        top_sender->Bus->put(tops.at(top_pointer));
        ++top_pointer;
        if(visual)
        {
            printf("> Issued T-OP...\n");
        }
    }
    if(!eop_sender->Bus->is_any_full() && eop_pointer < eops.size())
    {
        eop_sender->Bus->put(eops.at(eop_pointer));
        ++eop_pointer;
        if(visual)
        {
            printf("> Issued E-OP ...\n");
        }
    }
}

QVector<AllocOp> OpDispatcher::getAops() const
{
    return aops;
}
